package main

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"math/big"
	"net"
	"os"
	"time"
)

type CERT struct {
	// 证书流数据
	CERT []byte
	// 私钥(包含私钥对应的公钥，通过PrivateKey.PublicKey可调用)
	CERTKEY *rsa.PrivateKey
	// 证书公钥文件
	CERTPEM *bytes.Buffer
	// 证书私钥文件
	CERTKEYPEM *bytes.Buffer
	// 证书x509.Certificate模板文件
	CSR *x509.Certificate
}

func main() {
	// CA
	NewCA()
	cert, caKey, err := LoadPair("./ca/ca.cert.pem", "./ca/ca.key.pem")
	if err != nil {
		fmt.Println(cert)
		return
	}
	time.Sleep(time.Second * 1)

	// 中间CA
	NewMiddleCA(cert, caKey)
	time.Sleep(time.Second * 1)

	mcert, mcaKey, err := LoadPair("./mca/mca.cert.pem", "./mca/mca.key.pem")
	if err != nil {
		return
	}
	// OCSP
	NewOCSP(mcert, mcaKey)
	time.Sleep(time.Second * 1)

	// 服务端证书
	SignServerCert(mcert, mcaKey)
	time.Sleep(time.Second * 1)

	csr, err := ioutil.ReadFile("./shell/192.168.3.148.csr")
	if err != nil {
		fmt.Println(err)
		return
	}
	block, _ := pem.Decode(csr)

	SignCertWithCSR(block.Bytes, mcert, mcaKey)

	// 客户端证书
	SignClientCert(mcert, mcaKey)
}

func NewCA() *CERT {
	var (
		ca  = new(CERT)
		err error
	)
	// ca 证书
	ca.CSR = &x509.Certificate{
		Version:      3,
		SerialNumber: big.NewInt(time.Now().Unix()),
		Subject: pkix.Name{
			Country:            []string{"CN"},
			Province:           []string{"Jiangsu"},
			Locality:           []string{"Suzhou"},
			Organization:       []string{"Company, INC."},
			OrganizationalUnit: []string{"ECOVACS ORG"},
			CommonName:         "ecovacs.rootca.org",
			//Names:              nil,
			//ExtraNames:         nil,
		},

		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(50, 0, 0),
		BasicConstraintsValid: true,
		IsCA:                  true,
		// 仅能签发一级中级CA证书
		MaxPathLen:     1,
		MaxPathLenZero: false,
		// 支持签发证书以及吊销证书
		KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
	}
	// 私钥及公钥 rsa 格式
	ca.CERTKEY, _ = rsa.GenerateKey(rand.Reader, 4096)
	//caSelfSignedPublicKey := &ca.CERTKEY.PublicKey
	// 自签证书 []byte
	ca.CERT, err = x509.CreateCertificate(rand.Reader, ca.CSR, ca.CSR, &ca.CERTKEY.PublicKey, ca.CERTKEY)
	if err != nil {
		log.Println("create ca failed", err)
		return nil
	}
	cert, err := x509.ParseCertificate(ca.CERT)
	if err != nil {
		log.Println("create cert2 failed", err)
		return nil
	}
	pemData := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: cert.Raw,
	})
	if err = ioutil.WriteFile("./ca/ca.cert.pem", pemData, 0644); err != nil {
		panic(err)
	}

	pemKeyData := pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(ca.CERTKEY),
	})
	if err = ioutil.WriteFile("./ca/ca.key.pem", pemKeyData, 0644); err != nil {
		panic(err)
	}

	return ca
}

func NewMiddleCA(ca *x509.Certificate, caKey crypto.PrivateKey) *CERT {
	var (
		mca = new(CERT)
		err error
	)
	// ca 证书
	mca.CSR = &x509.Certificate{
		Version:      3,
		SerialNumber: big.NewInt(time.Now().Unix()),
		Subject: pkix.Name{
			Country:            []string{"CN"},
			Province:           []string{"Jiangsu"},
			Locality:           []string{"Suzhou"},
			Organization:       []string{"Company, INC."},
			OrganizationalUnit: []string{"ECOVACS ORG"},
			CommonName:         "ecovacs.middleca.org",
			Names: []pkix.AttributeTypeAndValue{
				pkix.AttributeTypeAndValue{
					Type:  nil,
					Value: nil,
				},
			},
			ExtraNames: nil,
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(10, 0, 0),
		BasicConstraintsValid: true,
		IsCA:                  true,
		// 仅能签发一级中级CA证书
		MaxPathLen:     0,
		MaxPathLenZero: true,
		// 支持签发证书以及吊销证书
		KeyUsage:   x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
		Extensions: nil,
	}
	// 私钥及公钥 rsa 格式
	mca.CERTKEY, _ = rsa.GenerateKey(rand.Reader, 4096)
	//caSelfSignedPublicKey := &ca.CERTKEY.PublicKey
	// 自签证书 []byte
	mca.CERT, err = x509.CreateCertificate(rand.Reader, mca.CSR, ca, &mca.CERTKEY.PublicKey, caKey)
	if err != nil {
		log.Println("create ca failed", err)
		return nil
	}
	cert, err := x509.ParseCertificate(mca.CERT)
	if err != nil {
		log.Println("create cert2 failed", err)
		return nil
	}
	pemData := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: cert.Raw,
	})
	if err = ioutil.WriteFile("./mca/mca.cert.pem", pemData, 0644); err != nil {
		panic(err)
	}

	pemKeyData := pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(mca.CERTKEY),
	})
	if err = ioutil.WriteFile("./mca/mca.key.pem", pemKeyData, 0644); err != nil {
		panic(err)
	}

	return mca
}

func NewOCSP(ca *x509.Certificate, caKey crypto.PrivateKey) *CERT {
	var (
		oscp = new(CERT)
		err  error
	)
	// ocsp 证书
	oscp.CSR = &x509.Certificate{
		Version:      3,
		SerialNumber: big.NewInt(time.Now().Unix()),
		Subject: pkix.Name{
			Country:            []string{"CN"},
			Province:           []string{"Jiangsu"},
			Locality:           []string{"Suzhou"},
			Organization:       []string{"Company, INC."},
			OrganizationalUnit: []string{"ECOVACS ORG"},
			CommonName:         "ecovacs.ocsp.org",
			//Names:              nil,
			//ExtraNames:         nil,
		},
		DNSNames:              []string{"ecovacs.ocsp.com"},
		IPAddresses:           []net.IP{net.IPv4(127, 0, 0, 1)},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(10, 0, 0),
		BasicConstraintsValid: true,
		IsCA:                  false,
		// 支持签发证书以及吊销证书
		KeyUsage:    x509.KeyUsageDigitalSignature,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning},
	}
	// 私钥及公钥 rsa 格式
	oscp.CERTKEY, _ = rsa.GenerateKey(rand.Reader, 2048)
	//caSelfSignedPublicKey := &ca.CERTKEY.PublicKey
	// 自签证书 []byte
	oscp.CERT, err = x509.CreateCertificate(rand.Reader, oscp.CSR, ca, &oscp.CERTKEY.PublicKey, caKey)
	if err != nil {
		log.Println("create ocsp failed", err)
		return nil
	}
	cert, err := x509.ParseCertificate(oscp.CERT)
	if err != nil {
		log.Println("create ocsp failed", err)
		return nil
	}
	pemData := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: cert.Raw,
	})
	if err = ioutil.WriteFile("./ocsp/ocsp.cert.pem", pemData, 0644); err != nil {
		panic(err)
	}

	pemKeyData := pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(oscp.CERTKEY),
	})
	if err = ioutil.WriteFile("./ocsp/ocsp.key.pem", pemKeyData, 0644); err != nil {
		panic(err)
	}

	return oscp
}

func SignServerCert(ca *x509.Certificate, caKey crypto.PrivateKey) {
	var (
		serverCert = new(CERT)
		err        error
	)

	// 待签署证书及其私钥公钥
	serverCert.CSR = &x509.Certificate{
		Version:      3,
		SerialNumber: big.NewInt(time.Now().Unix()),
		Subject: pkix.Name{
			Country:            []string{"CN"},
			Province:           []string{"jiangsu"},
			Locality:           []string{"555"},
			Organization:       []string{"555, INC."},
			OrganizationalUnit: []string{"555, INC."},
			CommonName:         "cadmus.com",
		},
		// DNS与IP非必填，且可以二选一，根据服务器使用域名还是ip来选择
		DNSNames:    []string{"*.cadmus.com"},
		IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},

		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(1, 0, 0),
		BasicConstraintsValid: true,
		IsCA:                  false,
		//SubjectKeyId: []byte{1, 2, 3, 4, 6},
		KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageContentCommitment | x509.KeyUsageDataEncipherment,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageCodeSigning},
	}
	serverCert.CERTKEY, _ = rsa.GenerateKey(rand.Reader, 2048)
	//certPublicKey := &cert.CERTKEY.PublicKey

	// 使用自签CA 对 证书签署
	serverCert.CERT, err = x509.CreateCertificate(rand.Reader, serverCert.CSR, ca, &serverCert.CERTKEY.PublicKey, caKey)
	if err != nil {
		log.Println("create cert2 failed", err)
		return
	}
	cert, err := x509.ParseCertificate(serverCert.CERT)
	if err != nil {
		log.Println("create cert2 failed", err)
		return
	}
	pemData := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: cert.Raw,
	})
	if err = ioutil.WriteFile("./server/cadmus.cert.pem", pemData, 0644); err != nil {
		panic(err)
	}

	pemKeyData := pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(serverCert.CERTKEY),
	})
	if err = ioutil.WriteFile("./server/cadmus.key.pem", pemKeyData, 0644); err != nil {
		panic(err)
	}
}

func SignClientCert(ca *x509.Certificate, caKey crypto.PrivateKey) {
	var (
		clientCert = new(CERT)
		err        error
	)

	// 待签署证书及其私钥公钥
	clientCert.CSR = &x509.Certificate{
		Version:      3,
		SerialNumber: big.NewInt(time.Now().Unix()),
		Subject: pkix.Name{
			Country:            []string{"CN"},
			Province:           []string{"jiangsu"},
			Locality:           []string{"777"},
			Organization:       []string{"777, INC."},
			OrganizationalUnit: []string{"777, INC."},
			CommonName:         "c107",
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(1, 0, 0),
		BasicConstraintsValid: true,
		IsCA:                  false,
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageContentCommitment | x509.KeyUsageDataEncipherment,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
	clientCert.CERTKEY, _ = rsa.GenerateKey(rand.Reader, 2048)
	//certPublicKey := &cert.CERTKEY.PublicKey

	// 使用自签CA 对 证书签署
	clientCert.CERT, err = x509.CreateCertificate(rand.Reader, clientCert.CSR, ca, &clientCert.CERTKEY.PublicKey, caKey)
	if err != nil {
		log.Println("create client failed", err)
		return
	}
	cert, err := x509.ParseCertificate(clientCert.CERT)
	if err != nil {
		log.Println("create client failed", err)
		return
	}
	pemData := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: cert.Raw,
	})
	if err = ioutil.WriteFile("./client/c107.cert.pem", pemData, 0644); err != nil {
		panic(err)
	}

	pemKeyData := pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(clientCert.CERTKEY),
	})
	if err = ioutil.WriteFile("./client/c107.key.pem", pemKeyData, 0644); err != nil {
		panic(err)
	}
}

func SignCertWithCSR(csr []byte, ca *x509.Certificate, caKey crypto.PrivateKey) {
	var (
		serverCert = new(CERT)
		err        error
	)

	request, err := x509.ParseCertificateRequest(csr)
	if err != nil {
		log.Println("ParseCertificateRequest  failed", err)
		return
	}

	// 待签署证书及其私钥公钥
	serverCert.CSR = &x509.Certificate{
		Version:      3,
		SerialNumber: big.NewInt(time.Now().Unix()),
		Subject:      request.Subject,
		// DNS与IP非必填，且可以二选一，根据服务器使用域名还是ip来选择
		DNSNames:              request.DNSNames,
		IPAddresses:           request.IPAddresses,
		SignatureAlgorithm:    request.SignatureAlgorithm,
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(1, 0, 0),
		BasicConstraintsValid: true,
		IsCA:                  false,
		//SubjectKeyId: []byte{1, 2, 3, 4, 6},
		KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageContentCommitment | x509.KeyUsageDataEncipherment,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageCodeSigning},
	}
	serverCert.CERTKEY, _ = rsa.GenerateKey(rand.Reader, 2048)
	//certPublicKey := &cert.CERTKEY.PublicKey

	// 使用自签CA 对 证书签署
	serverCert.CERT, err = x509.CreateCertificate(rand.Reader, serverCert.CSR, ca, request.PublicKey, caKey)
	if err != nil {
		log.Println("create cert2 failed", err)
		return
	}
	cert, err := x509.ParseCertificate(serverCert.CERT)
	if err != nil {
		log.Println("create cert2 failed", err)
		return
	}
	pemData := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: cert.Raw,
	})
	if err = ioutil.WriteFile("./server/cadmus222.cert.pem", pemData, 0644); err != nil {
		panic(err)
	}

	pemKeyData := pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(serverCert.CERTKEY),
	})
	if err = ioutil.WriteFile("./server/cadmus222.key.pem", pemKeyData, 0644); err != nil {
		panic(err)
	}
}

func initSignCa() *CERT {
	var ca = new(CERT)

	// 证书公钥
	caCert, err := ioutil.ReadFile("./res/middleca.cert.pem")
	if err != nil {
		fmt.Println(err)
		return nil
	}
	ca.CSR = &x509.Certificate{
		Raw: caCert,
	}
	// 证书私钥
	caKey, err := ioutil.ReadFile("./res/middleca.key.pem")
	if err != nil {
		fmt.Println(err)
		return nil
	}
	block, _ := pem.Decode(caKey)

	ca.CERTKEY, err = x509.ParsePKCS1PrivateKey(block.Bytes)
	return ca
}

// LoadPair 从PEM文件读取证书
func LoadPair(certFile, keyFile string) (cert *x509.Certificate, privateKey crypto.PrivateKey, err error) {
	if len(certFile) == 0 && len(keyFile) == 0 {
		return nil, nil, errors.New("cert or key has not provided")
	}

	// load cert and key by tls.LoadX509KeyPair
	tlsCert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return
	}
	// 返回私钥
	privateKey = tlsCert.PrivateKey

	cert, err = x509.ParseCertificate(tlsCert.Certificate[0])
	return
}

func Write(cert *CERT, file string) error {
	keyFileName := file + ".key"
	certFIleName := file + ".crt"
	kf, err := os.Create(keyFileName)
	if err != nil {
		return err
	}
	defer kf.Close()

	if _, err := kf.Write(cert.CERTKEYPEM.Bytes()); err != nil {
		return err
	}

	cf, err := os.Create(certFIleName)
	if err != nil {
		return err
	}
	if _, err := cf.Write(cert.CERTPEM.Bytes()); err != nil {
		return err
	}
	return nil
}
